import { RepoLink, Link } from '@brillout/docpress'

To internationalize (i18n) your app:
1. Use the <Link href="/onBeforeRoute">`onBeforeRoute()` hook</Link>.
2. Update your `<Link>` component.


#### 1. `onBeforeRoute()`

```ts
// pages/+onBeforeRoute.ts
// Environment: server & client

export { onBeforeRoute }

import type { PageContext } from 'vike/types'
import { modifyUrl } from 'vike/modifyUrl'
import type { Url } from 'vike/types'

function onBeforeRoute(pageContext: PageContext) {
  const { urlWithoutLocale, locale } = extractLocale(pageContext.urlParsed)
  return {
    pageContext: {
      // Make locale available as pageContext.locale
      locale,
      // Vike's router will use pageContext.urlLogical instead of pageContext.urlOriginal and
      // the locale is removed from pageContext.urlParsed
      urlLogical: urlWithoutLocale
    }
  }
}

function extractLocale(url: Url) {
  const { pathname } = url

  // Determine the locale, for example:
  //  /en-US/film/42 => en-US
  //  /de-DE/film/42 => de-DE
  // @detype-uncomment const locale = /* ... */

  // Remove the locale, for example:
  //  /en-US/film/42 => /film/42
  //  /de-DE/film/42 => /film/42
  // @detype-uncomment const pathnameWithoutLocale = /* ... */

  // Reconstruct full URL
  const urlWithoutLocale = modifyUrl(url.href, { pathname: pathnameWithoutLocale })

  return { locale, urlWithoutLocale }
}
```

> See also: <Link href="/modifyUrl" />

> This only removes the locale from `pageContext.urlParsed`: it doesn't modify the actual URL in the browser.

Upon rendering a page, `onBeforeRoute()` is the first hook that Vike calls. This means that
all other hooks can use `pageContext.locale` and the updated `pageContext.urlParsed`,
as well as all your UI components (with <Link href="/usePageContext">`usePageContext()`</Link>).

This technique also works with:
- `?lang=fr` query parameters
- `domain.fr` domain TLDs
- `Accept-Language: fr-FR` headers
   > The `Accept-Language` header can be used for <Link href="/redirect">redirecting the user</Link> to the right localized URL (e.g. URL `/about` + Header `Accept-Language: de-DE` => redirect to `/de-DE/about`). Once the user is redirected to a localized URL, you can use the technique described above.
   >
   > Using the `Accept-Language` header to show different languages for the same URL is considered bad practice for SEO and UX. It's recommended to use `Accept-Language` only to redirect the user.

#### 2. `<Link>`

Update your `<Link>` component, for example:

```tsx
// components/Link.ts

import { usePageContext } from 'vike-react/usePageContext' // or vike-vue / vike-solid
import { localeDefault } from '../i18n'

export function Link({ href, locale, ...props }: { locale: string } & AnchorElementAttributes) {
  const pageContext = usePageContext()
  locale = locale ?? pageContext.locale
  if (locale !== localeDefault) {
    href = '/' + locale + href
  }
  return <a href={href} {...props} />
}
```

## Examples

 - <RepoLink path='/examples/i18n/' />
 - [github.com/crummy/vite-ssr-i18n](https://github.com/crummy/vite-ssr-i18n)
   > vite-plugin-ssr was the [previous name of Vike](https://vite-plugin-ssr.com/vike).


## Pre-rendering

If you use <Link text="pre-rendering" href="/pre-rendering" /> then, in addition to defining <Link href="/onBeforeRoute">`onBeforeRoute()`</Link>, you also need to
define the <Link href="/onPrerenderStart">`onPrerenderStart()`</Link> hook:

```ts
// pages/+onPrerenderStart.ts
// Environment: build-time

export { onPrerenderStart }

import type { PrerenderContext, PageContextServer } from 'vike/types'

const locales = ['en-US', 'de-DE', 'fr-FR']
const localeDefault = 'en-US'

async function onPrerenderStart(prerenderContext: PrerenderContext) {
  const pageContexts: PageContextServer[] = []
  prerenderContext.pageContexts.forEach((pageContext) => {
    // Duplicate pageContext for each locale
    locales.forEach((locale) => {
      // Localize URL
      let { urlOriginal } = pageContext
      if (locale !== localeDefault) {
        urlOriginal = `/${locale}${pageContext.urlOriginal}`
      }
      pageContexts.push({
        ...pageContext,
        urlOriginal,
        // Set pageContext.locale
        locale
      })
    })
  })
  return {
    prerenderContext: {
      pageContexts
    }
  }
}
```

See <RepoLink path='/examples/i18n/' /> for an example using `onPrerenderStart()`.

Your <Link text={<><code>onBeforePrerenderStart()</code> hooks</>} href="/onBeforePrerenderStart" /> (if you use any) return URLs without any locale (e.g. `onBeforePrerenderStart()` returning `/product/42`). Instead, it's your `onPrerenderStart()` hook that duplicates and modifies URLs for each locale (e.g. duplicating `/product/42` into `/en-US/product/42`, `/de-DE/product/42`, `/fr-FR/product/42`).

```ts
// pages/product/+onBeforePrerenderStart.ts

export { onBeforePrerenderStart }

async function onBeforePrerenderStart(): Promise<string[]> {
  const products = await Product.findAll()
  const URLs: string[] = products.map(({ id }) => '/product/' + id)
  // You don't add the locale here (it's your onPrerenderStart() hook that adds the locales)
  return URLs
}
```

Essentially, you use `onBeforePrerenderStart()` to determine URLs and/or load data, and use `onPrerenderStart()` to
manipulate localized URLs and set `pageContext.locale`.

> `onPrerenderStart()` is a global hook you can define only once, while `onBeforePrerenderStart()` is a per-page hook you can define multiple times.

Alternatively, if you need to load data that depends on localization, instead of `onPrerenderStart()` you can use
`onBeforePrerenderStart()` to localize <Link href="/pageContext#data">`pageContext.data`</Link>:

```ts
// pages/product/+onBeforePrerenderStart.ts

// This example doesn't use onPrerenderStart() but, instead,
// uses onBeforePrerenderStart() to duplicate and localize URLs and their pageContext
export { onBeforePrerenderStart }

type PageContext = {
  locale: string
  data?: Record<string, unknown>
} & Record<string, unknown>
type Return = { url: string, pageContext: PageContext }[]

async function onBeforePrerenderStart(): Promise<Return> {
  // Load data
  const products = await Product.findAll()

  // Set pageContext + localize
  const urlsWithPageContext: Return = []
  products.forEach(product => {
    ['en-US', 'de-DE', 'fr-FR'].forEach(locale => {
      urlsWithPageContext.push({
        url: `/${locale}/product/${product.id}`,
        pageContext: {
          locale,
          product,
          data: {
            product: {
              name: product.name,
              description: product.description,
              price: product.price,
              // ...
            }
          }
        }
      })
    })
  })

  return urlsWithPageContext
}
```

You may still need to use `onPrerenderStart()` for localizing static pages that don't load data:

```ts
// pages/+onPrerenderStart.ts

export { onPrerenderStart }

import assert from 'assert'
import type { PrerenderContext, PageContextServer } from 'vike/types'

const locales = ['en-US', 'de-DE', 'fr-FR']

async function onPrerenderStart(prerenderContext: PrerenderContext) {
  const pageContexts: PageContextServer[] = []
  prerenderContext.pageContexts.forEach((pageContext) => {
    if(pageContext.locale) {
      // Already localized by one of your onBeforePrerenderStart() hooks
      assert(pageContext.urlOriginal.startsWith(`/${pagecontext.locale}/`))
      pageContexts.push(pageContext)
    } else {
      // Duplicate pageContext for each locale
      locales.forEach((locale) => {
        // Localize URL and pageContext
        pageContexts.push({
          ...pageContext,
          urlOriginal: `/${locale}${pageContext.urlOriginal}`,
          locale
        })
      })
    }
  })
  return {
    prerenderContext: {
      pageContexts
    }
  }
}
```


## `lang`

You can use the `lang` setting to define the value of the `<html lang>` attribute, see <Link href="/lang" />.


## Split locale data

To reduce your client bundle sizes, you can load only the translation of the current language — instead of loading the translations of all languages at once.

For example by using <Link href="/onBeforeRender#environment">`+onBeforeRender.shared.js`</Link>:

```ts
// pages/+onBeforeRender.shared.ts
// Environment: server & client

import type { PageContext } from 'vike/types'

export async function onBeforeRender(pageContext: PageContext) {
  pageContext.localeData = await import(`../translations/${pageContext.locale}.ts`)
}
```

Alternatively, you can define a server-only `+onBeforeRender.js` hook (without `.shared.js` suffix) and add `pageContext.localeData` to <Link href="/passToClient">`passToClient`</Link>.


## See also

- <Link href="/head-tags#internationalization" />
- <Link href="/lang" />
- <Link href="/onBeforeRoute" />
- <Link href="/modifyUrl" />
- <Link href="/tools#internationalization-i18n" doNotInferSectionTitle />
